package zap

import (
	"context"
	"fmt"
	"gitee.com/pearl-zz/go-manage-system-core/logger"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"io"
	"os"
	"sync"
	"time"
)

type zapLog struct {
	sync.RWMutex
	cfg    zap.Config
	zap    *zap.Logger
	opts   logger.Options
	fields map[string]interface{}
}

// NewLogger build a new logger based on options
func NewLogger(opts ...logger.Option) (logger.Logger, error) {
	options := logger.Options{
		Level:   logger.InfoLevel,
		Fields:  make(map[string]interface{}),
		Output:  os.Stderr,
		Context: context.Background(),
	}
	l := &zapLog{opts: options}
	if err := l.Init(opts...); err != nil {
		return nil, err
	}
	return l, nil
}

func (l *zapLog) Init(opts ...logger.Option) error {
	for _, o := range opts {
		o(&l.opts)
	}

	skip, ok := l.opts.Context.Value(callerSkipKey{}).(int)
	if !ok || skip < 1 {
		skip = 1
	}

	writer, ok := l.opts.Context.Value(writeKey{}).(io.Writer)
	if !ok {
		writer = os.Stdout
	}

	infoPath, ok := l.opts.Context.Value(infoPathKey{}).(string)
	if !ok {
		infoPath = ""
	}

	errPath, ok := l.opts.Context.Value(errPathKey{}).(string)
	if !ok {
		errPath = ""
	}

	zapConfig := zap.NewProductionConfig()
	// 配置默认选项：日志级别大写，时间输出key为timestamp，value格式为2006-01-02 15:04:05
	zapConfig.EncoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
	zapConfig.EncoderConfig.TimeKey = "timestamp"
	zapConfig.EncoderConfig.EncodeTime = func(t time.Time, encoder zapcore.PrimitiveArrayEncoder) {
		encoder.AppendString(t.Format("2006-01-02 15:04:05"))
	}

	if config, ok := l.opts.Context.Value(configKey{}).(zap.Config); ok {
		zapConfig = config
	}
	if config, ok := l.opts.Context.Value(encoderConfigKey{}).(zapcore.EncoderConfig); ok {
		zapConfig.EncoderConfig = config
	}

	// Set log level if not default
	zapConfig.Level = zap.NewAtomicLevel()
	if l.opts.Level != logger.InfoLevel {
		zapConfig.Level.SetLevel(loggerToZapLevel(l.opts.Level))
	}

	// 控制台默认输出JSON格式
	cores := make([]zapcore.Core, 1)
	cores = append(cores, zapcore.NewCore(zapcore.NewJSONEncoder(zapConfig.EncoderConfig), zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout)), zapConfig.Level))

	if writer != os.Stdout {
		cores = append(cores, zapcore.NewCore(zapcore.NewJSONEncoder(zapConfig.EncoderConfig), zapcore.NewMultiWriteSyncer(zapcore.AddSync(writer)), zapConfig.Level))
	}

	if infoPath != "" {
		// 自定义日志级别：自定义Info级别
		infoLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
			return lvl < zapcore.WarnLevel && lvl >= loggerToZapLevel(l.opts.Level)
		})
		infoWriter := getWriter(infoPath)
		cores = append(cores, zapcore.NewCore(zapcore.NewJSONEncoder(zapConfig.EncoderConfig), zapcore.AddSync(infoWriter), infoLevel))
	}

	if errPath != "" {
		// 自定义日志级别：自定义Warn级别
		warnLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
			return lvl >= zapcore.WarnLevel && lvl >= loggerToZapLevel(l.opts.Level)
		})
		warnWriter := getWriter(errPath)
		cores = append(cores, zapcore.NewCore(zapcore.NewJSONEncoder(zapConfig.EncoderConfig), zapcore.AddSync(warnWriter), warnLevel))
	}

	teeCore := zapcore.NewTee(cores...)
	log := zap.New(teeCore, zap.AddCaller(), zap.AddCallerSkip(skip), zap.AddStacktrace(zap.DPanicLevel))

	// Adding seed fields if exist
	if l.opts.Fields != nil {
		var data []zap.Field
		for k, v := range l.opts.Fields {
			data = append(data, zap.Any(k, v))
		}
		log = log.With(data...)
	}

	l.cfg = zapConfig
	l.zap = log
	l.fields = make(map[string]interface{})
	return nil
}

func (l *zapLog) Options() logger.Options {
	return l.opts
}

func (l *zapLog) Fields(fields map[string]interface{}) logger.Logger {
	l.Lock()

	newFields := make(map[string]interface{}, len(l.fields))
	for k, v := range l.fields {
		newFields[k] = v
	}
	l.Unlock()
	for k, v := range fields {
		newFields[k] = v
	}

	data := make([]zap.Field, 0, len(newFields))
	for k, v := range fields {
		data = append(data, zap.Any(k, v))
	}

	zl := &zapLog{
		cfg:    l.cfg,
		zap:    l.zap,
		opts:   l.opts,
		fields: newFields,
	}

	return zl
}

func (l *zapLog) Log(level logger.Level, args ...interface{}) {
	l.RLock()
	data := make([]zap.Field, 0, len(l.fields))
	for k, v := range l.fields {
		data = append(data, zap.Any(k, v))
	}
	l.RUnlock()

	lvl := loggerToZapLevel(level)
	msg := fmt.Sprint(args...)
	switch lvl {
	case zap.DebugLevel:
		l.zap.Debug(msg, data...)
	case zap.InfoLevel:
		l.zap.Info(msg, data...)
	case zap.WarnLevel:
		l.zap.Warn(msg, data...)
	case zap.ErrorLevel:
		l.zap.Error(msg, data...)
	case zap.FatalLevel:
		l.zap.Fatal(msg, data...)
	}
}

func (l *zapLog) Logf(level logger.Level, format string, args ...interface{}) {
	l.RLock()
	data := make([]zap.Field, 0, len(l.fields))
	for k, v := range l.fields {
		data = append(data, zap.Any(k, v))
	}
	l.RUnlock()

	lvl := loggerToZapLevel(level)
	msg := fmt.Sprintf(format, args...)

	switch lvl {
	case zap.DebugLevel:
		l.zap.Debug(msg, data...)
	case zap.InfoLevel:
		l.zap.Info(msg, data...)
	case zap.WarnLevel:
		l.zap.Warn(msg, data...)
	case zap.ErrorLevel:
		l.zap.Error(msg, data...)
	case zap.FatalLevel:
		l.zap.Fatal(msg, data...)
	}
}

func (l *zapLog) String() string {
	return "zap"
}

func loggerToZapLevel(level logger.Level) zapcore.Level {
	switch level {
	case logger.TraceLevel, logger.DebugLevel:
		return zap.DebugLevel
	case logger.InfoLevel:
		return zap.InfoLevel
	case logger.WarnLevel:
		return zap.WarnLevel
	case logger.ErrorLevel:
		return zap.ErrorLevel
	case logger.FatalLevel:
		return zap.FatalLevel
	default:
		return zap.InfoLevel
	}
}

func zapToLoggerLevel(level zapcore.Level) logger.Level {
	switch level {
	case zap.DebugLevel:
		return logger.DebugLevel
	case zap.InfoLevel:
		return logger.InfoLevel
	case zap.WarnLevel:
		return logger.WarnLevel
	case zap.ErrorLevel:
		return logger.ErrorLevel
	case zap.FatalLevel:
		return logger.FatalLevel
	default:
		return logger.InfoLevel
	}
}
